
use std::{collections::btree_map::Values, env::set_var};
use sodiumoxide::base64;

use log::{info, warn};
use env_logger;

const VERSION_LEN: usize = 2;

fn get_auto_id() -> Option<String> {
        
        if let Ok(Some(ma)) = mac_address::mac_address_by_name("以太网 2") {
            let idstr = format!("{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",ma.bytes()[0],ma.bytes()[1],ma.bytes()[2],ma.bytes()[3],ma.bytes()[4],ma.bytes()[5]);
            Some(idstr.to_string())
        } else {
            None
        }

}

pub fn get_uuid() -> Vec<u8> {
    if let Ok(id) = machine_uid::get() {
        return id.into();
    }
    return [].to_vec();
}

pub fn symmetric_crypt(data: &[u8], encrypt: bool) -> Result<Vec<u8>, ()> {
    use sodiumoxide::crypto::secretbox;
    use std::convert::TryInto;

    let mut keybuf = crate::get_uuid();
    
//    println!("Vector (Hex): {:?}", keybuf.iter().map(|x| format!("{:X}", x)).collect::<Vec<String>>());
    keybuf.resize(secretbox::KEYBYTES, 0);
    let key = secretbox::Key(keybuf.try_into().map_err(|_| ())?);
    let nonce = secretbox::Nonce([0; secretbox::NONCEBYTES]);

    if encrypt {
        Ok(secretbox::seal(data, &nonce, &key))
    } else {
        secretbox::open(data, &nonce, &key)
    }
}

fn encrypt(v: &[u8], max_len: usize) -> Result<String, ()> {
    if !v.is_empty() && v.len() <= max_len {
        symmetric_crypt(v, true).map(|v| base64::encode(v, base64::Variant::Original))
    } else {
        Err(())
    }
}

fn decrypt(v: &[u8]) -> Result<Vec<u8>, ()> {
    if !v.is_empty() {
        base64::decode(v, base64::Variant::Original).and_then(|v| symmetric_crypt(&v, false))
    } else {
        Err(())
    }
}

pub fn encrypt_str_or_original(s: &str, version: &str, max_len: usize) -> String {
    if decrypt_str_or_original(s, version).1 {
        log::error!("Duplicate encryption!");
        return s.to_owned();
    }
    if s.bytes().len() > max_len {
        return String::default();
    }
    if version == "00" {
        if let Ok(s) = encrypt(s.as_bytes(), max_len) {
            return version.to_owned() + &s;
        }
    }
    s.to_owned()
}

// String: password
// bool: whether decryption is successful
// bool: whether should store to re-encrypt when load
// note: s.len() return length in bytes, s.chars().count() return char count
//       &[..2] return the left 2 bytes, s.chars().take(2) return the left 2 chars
pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool, bool) {
    if s.len() > VERSION_LEN {
        if s.starts_with("00") {
            if let Ok(v) = decrypt(s[VERSION_LEN..].as_bytes()) {
                return (
                    String::from_utf8_lossy(&v).to_string(),
                    true,
                    "00" != current_version,
                );
            }
        }
    }

    (s.to_owned(), false, !s.is_empty())
}


fn  get_id_key(data :&String){
    let version = "00";
    let max_len = 128;
    let encrypted = encrypt_str_or_original(data, version, max_len);
    let (decrypted, succ, store) = decrypt_str_or_original(&encrypted, version);

    if succ == true{
        print!("encrypted,decrypted data:{} {}  {}",data,encrypted,decrypted);
    }else {
        print!("encrypted,decrypted data:{} {}  {}",data,encrypted,decrypted);
    }
}

fn  get_id_encode(data :&String){
    let version = "00";
    let max_len = 128;
    let encrypted = encrypt_str_or_original(data, version, max_len);

    print!("encrypted data:{}",encrypted);

}

fn  get_id_decode(data :&String){
    let version = "00";
    let max_len = 128;
    let (decrypted, succ, store) = decrypt_str_or_original(&data, version);

    if succ == true{
        print!("decrypted data:{}",decrypted);
    }else {
        print!("decrypted data: error");
    }
}


/* 
fn main() {
    env_logger::init();
    set_var("RUST_LOG", "debug");
    let mut value="".to_string();
    if  let Some(id) = get_auto_id() {
        println!("jiangyibo {}",id);
        value = id;
    }
    get_id_key(&value);
    println!("Hello, world!");
    
}
*/


use clap::{Arg, ArgMatches, Command};
use chrono::{Local, TimeZone};
use urlencoding::{decode, encode};
 
fn main() {
    let matches = Command::new("my_dev_tool")
        .version("1.0")
        .author("Your Name <your.email@example.com>")
        .about("Developer's tool for urlencode and time format!")
        .subcommand_required(true)
        .arg_required_else_help(true)
        .subcommand(
            Command::new("idencode")
                .about("id-encode a string")
                .arg(Arg::new("input").help("String to encode").required(false)),
        )
        .subcommand(
            Command::new("iddecode")
                .about("id-decode a string")
                .arg(Arg::new("input").help("String to decode").required(true)),
        )
        .subcommand(
            Command::new("timestamp")
                .about("Convert a UNIX timestamp to local datetime")
                .arg(Arg::new("timestamp").help("UNIX timestamp").required(true)),
        )
        .get_matches();
 
    match matches.subcommand() {
        Some(("idencode", sub_matches)) => url_encode(sub_matches),
        Some(("iddecode", sub_matches)) => url_decode(sub_matches),
        Some(("timestamp", sub_matches)) => convert_timestamp(sub_matches),
        _ => unreachable!(),
    }
}
 
fn url_encode(matches: &ArgMatches) {
    if let Some(mut input) = matches.get_one::<String>("input") {
        if let Some(data )= get_auto_id() {
           println!("id : {}",data.to_string());
           get_id_encode(&data);
        }
    }
}
 
fn url_decode(matches: &ArgMatches) {
    if let Some(input) = matches.get_one::<String>("input") {
        get_id_decode(input);
    }
}
 
fn convert_timestamp(matches: &ArgMatches) {
    if let Some(timestamp_str) = matches.get_one::<String>("timestamp") {
        let timestamp = timestamp_str.parse::<i64>().unwrap();
        let datetime = Local.timestamp_opt(timestamp, 0).unwrap();
      //  println!("{}", datetime.to_rfc3339());
    }
}


